﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using AdvanceLib.Core;
using AdvanceLib.AddIn;
using AdvanceLib.ScriptCore;
using AdvanceLib.ScriptCore.SystemCommands;

namespace AdvanceLib
{
    public enum ByteOrderMark
    {
        Default = 0,
        LittleEndian = 1
    }
    public class ScriptHost
    {
        #region Constructors
        public ScriptHost()
        {
            this.Offsets = new Dictionary<string, long>();
            this.Errors = new List<DebugError>();

            this.SystemCommandInitialization = "#";

            this._watch = new Stopwatch();

            this.ReloadPlugins();
        }
        public ScriptHost(Stream romStream) : this()
        {
            this.ROMStream = romStream;
        }
        #endregion

        #region Fields
        private Stream _romStream;
        private FreeSpaceFinder _freeSpaceFinder;
        private IExtensionProvider _currentProvider;
        private Settings _settings;

        private Stopwatch _watch;
        #endregion

        #region Properties
        public ExtensionManager ExtensionManager { get; set; }

        public ByteOrderMark ByteOrderMark { get; set; }
        public Dictionary<string, long> Offsets { get; set; }
        public Stream ROMStream
        {
            get { return this._romStream; }
            set
            {
                this._romStream = value;
                this._freeSpaceFinder = new FreeSpaceFinder(this._romStream);
            }
        }

        public List<DebugError> Errors { get; set; }

        public long ElapsedTicks { get; set; }
        #endregion

        #region Constant Properties
        public string SystemCommandInitialization { get; set; }
        #endregion

        #region Compile & Parse Methods
        public Script CompileScript(string source)
        {
            this._watch.Reset();
            this._watch.Start();

            Script output = Script.Empty;
            IExtensionProvider provider = this.GetProvider(Constants.ROOT_PROVIDER);

            string formattedSource = provider.Formatter.Format(source);
            DebugResult debug = provider.Debugger.Debug(formattedSource);

            if (debug.IsValid)
            {
                List<ICommand> commands = provider.Parser.Parse(formattedSource);

                foreach (ICommand command in commands)
                {
                    if (command is ISystemCommand)
                    {
                        ISystemCommand systemCommand = command as ISystemCommand;
                        systemCommand.Execute(this, systemCommand.ParameterValues);
                    }
                    if (command is ISystemSection)
                    {
                        ISystemSection systemSection = command as ISystemSection;

                        ScriptSection currentSection = this.CompileSection(
                            systemSection.Source,
                            systemSection.BaseLine,
                            systemSection
                        );

                        if (currentSection != null)
                        {
                            this.Offsets.Add(currentSection.Name, currentSection.Offset);
                            output.Sections.Add(currentSection);
                        }
                        else
                        {
                            this.SetTicks();
                            return null;
                        }
                    }
                }
            }
            else { this.Errors.AddRange(debug.Errors); }

            this.SetTicks();

            if (output != Script.Empty)
                return output.Compile();
            else
                return null;
        }
        public void ExecuteLine(string source)
        {
            string backupSystemCommandInitialization = this.SystemCommandInitialization;
            this.SystemCommandInitialization = string.Empty;

            IExtensionProvider provider = this.GetProvider(Constants.ROOT_PROVIDER);

            string formattedSource = provider.Formatter.Format(source);
            DebugResult debug = provider.Debugger.Debug(formattedSource);

            if (debug.IsValid)
            {
                List<ICommand> commands = provider.Parser.Parse(formattedSource);
                this.SystemCommandInitialization = backupSystemCommandInitialization;

                foreach (ICommand command in commands)
                {
                    if (command is ISystemCommand)
                    {
                        ISystemCommand systemCommand = command as ISystemCommand;
                        systemCommand.Execute(this, systemCommand.ParameterValues);
                    }
                }
            }
            else { this.Errors.AddRange(debug.Errors); }
        }
        public ScriptSection CompileSection(string sectionSource, int baseLine, ISystemSection systemSection)
        {
            IExtensionProvider provider = systemSection.GetProvider();
            provider.Host = this;

            string formattedSource = provider.Formatter.Format(sectionSource);
            DebugResult debug = provider.Debugger.Debug(formattedSource);

            if (debug.IsValid)
            {
                ScriptSection section = new ScriptSection();
                section.Commands = provider.Parser.Parse(formattedSource);
                section.Source = sectionSource;
                section.Line = baseLine;

                section.CalculateSize();
                section.Offset = this.GetFreeOffset(section.Size);
                section.Name = systemSection.SectionName;

                return section;
            }

            debug.Errors.ForEach(error => error.Line += baseLine);
            this.Errors.AddRange(debug.Errors);

            return null;
        }
        public string DecompileScript(byte[] data)
        {
            throw new NotImplementedException();
        }
        public void WriteScriptToROM(Script script)
        {
            BinaryWriter writer = new BinaryWriter(this.ROMStream);
            foreach (KeyValuePair<long, byte[]> pair in script.Data)
            {
                long offset = pair.Key;
                byte[] data = pair.Value;

                this.ROMStream.Position = offset;
                writer.Write(data);
            }
            writer.Close();
        }
        #endregion

        #region Methods
        public long GetFreeOffset(int length)
        {
            if (this.ROMStream != null && this.ROMStream.CanRead)
            {
                FreeSpaceFinder finder = this._freeSpaceFinder;
                finder.InitialOffset = (long)Settings.Instance["DynamicOffset"];

                return finder.Search(length);
            }
            else
            {
                throw new ArgumentNullException("ROMStream has to be a valid stream.");
            }
        }
        public long GetOffset(string sectionName)
        {
            if (this.Offsets.ContainsKey(sectionName))
            {
                return this.Offsets[sectionName];
            }
            return -1;
        }
        public IExtensionProvider GetProvider(string name)
        {
            IExtensionProvider provider = this.ExtensionManager.GetExtensionByName(name);
            provider.Host = this;

            return provider;
        }
        public void OpenROM(string fileName)
        {
            if (this.ROMStream != null) this.ROMStream.Close();
            this.ROMStream = new FileStream(fileName, FileMode.Open);
        }
        public void ReloadPlugins()
        {
            this.ExtensionManager = new ExtensionManager(true);
            this.ExtensionManager.SystemCommands.AddRange(
                new List<ISystemCommand>()
                {
                    new StartPointer(),
                    new RootSection(),
                    new Dynamic()
                }
            );
        }
        public void Clear()
        {
            this.Errors.Clear();            
            this.Offsets.Clear();
        }
        private void SetTicks()
        {
            this._watch.Stop();
            this.ElapsedTicks = this._watch.ElapsedTicks - this._freeSpaceFinder.ElapsedTicks;
        }
        #endregion
    }
}
